 /*******************************************************************************
  * Copyright (c) 2000, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.core.runtime;

 import java.io.*;
 import java.util.*;
 import org.eclipse.core.internal.preferences.PreferencesService;
 import org.eclipse.core.internal.preferences.PrefsMessages;
 import org.eclipse.core.runtime.preferences.*;
 import org.eclipse.osgi.util.NLS;

 /**
  * A table of preference settings, mapping named properties to values. Property
  * names are non-empty strings; property values can be either booleans,
  * non-null strings, or values of one of the primitive number types.
  * The table consists of two, sparse, layers: the lower layer holds default values
  * for properties, and the upper layer holds explicitly set values for properties.
  * Normal retrieval looks for an explicitly set value for the given property in
  * the upper layer; if there is nothing for that property in the upper layer, it
  * next looks for a default value for the given property in the lower layer; if
  * there is nothing for that property in the lower layer, it returns a standard
  * default-default value. The default-default values for the primitive types are
  * as follows:
  * <ul>
  * <li><code>boolean</code> = <code>false</code></li>
  * <li><code>double</code> = <code>0.0</code></li>
  * <li><code>float</code> = <code>0.0f</code></li>
  * <li><code>int</code> = <code>0</code></li>
  * <li><code>long</code> = <code>0L</code></li>
  * <li><code>String</code> = <code>""</code> (the empty string)</li>
  * </ul>
  * <p>
  * Internally, all properties values (in both layers) are stored as strings.
  * Standard conversions to and from numeric and boolean types are performed on
  * demand.
  * </p>
  * <p>
  * The typical usage is to establish the defaults for all known properties
  * and then restore previously stored values for properties whose values
  * were explicitly set. The existing settings can be changed and new properties
  * can be set (<code>setValue</code>). If the values specified is the same as
  * the default value, the explicit setting is deleted from the top layer.
  * It is also possible to reset a property value back to the default value
  * using <code>setToDefault</code>. After the properties have been modified,
  * the properties with explicit settings are written to disk. The default values
  * are never saved. This two-tiered approach
  * to saving and restoring property setting minimizes the number of properties
  * that need to be persisted; indeed, the normal starting state does not require
  * storing any properties at all. It also makes it easy to use different
  * default settings in different environments while maintaining just those
  * property settings the user has adjusted.
  * </p>
  * <p>
  * A property change event is reported whenever a property's value actually
  * changes (either through <code>setValue</code>, <code>setToDefault</code>).
  * Note, however, that manipulating default values (with <code>setDefault</code>)
  * does not cause any events to be reported.
  * </p>
  * <p>
  * Clients may instantiate this class. This class was not designed to be
  * subclassed.
  * </p>
  * <p>
  * The implementation is based on a pair of internal
  * <code>java.util.Properties</code> objects, one holding explicitly set values
  * (set using <code>setValue</code>), the other holding the default values
  * (set using <code>setDefaultValue</code>). The <code>load</code> and
  * <code>store</code> methods persist the non-default property values to
  * streams (the default values are not saved).
  * </p>
  * <p>
  * If a client sets a default value to be equivalent to the default-default for that
  * type, the value is still known to the preference store as having a default value.
  * That is, the name will still be returned in the result of the <code>defaultPropertyNames</code>
  * and <code>contains</code> methods.
  * </p>
  *
  * @since 2.0
  */
 public class Preferences {

     /**
      * The default-default value for boolean properties (<code>false</code>).
      */
     public static final boolean BOOLEAN_DEFAULT_DEFAULT = false;

     /**
      * The default-default value for double properties (<code>0.0</code>).
      */
     public static final double DOUBLE_DEFAULT_DEFAULT = 0.0;

     /**
      * The default-default value for float properties (<code>0.0f</code>).
      */
     public static final float FLOAT_DEFAULT_DEFAULT = 0.0f;

     /**
      * The default-default value for int properties (<code>0</code>).
      */
     public static final int INT_DEFAULT_DEFAULT = 0;

     /**
      * The default-default value for long properties (<code>0L</code>).
      */
     public static final long LONG_DEFAULT_DEFAULT = 0L;

     /**
      * The default-default value for String properties (<code>""</code>).
      */
     public static final String STRING_DEFAULT_DEFAULT = ""; //$NON-NLS-1$

     /**
      * The string representation used for <code>true</code>
      * (<code>"true"</code>).
      */
     protected static final String TRUE = "true"; //$NON-NLS-1$

     /**
      * The string representation used for <code>false</code>
      * (<code>"false"</code>).
      */
     protected static final String FALSE = "false"; //$NON-NLS-1$

     /**
      * Singleton empty string array (optimization)
      */
     private static final String [] EMPTY_STRING_ARRAY = new String [0];

     /**
      * The simple identifier constant (value "<code>preferences</code>") of
      * the extension point of the Core Runtime plug-in where plug-ins declare
      * extensions to the preference facility. A plug-in may define any number
      * of preference extensions.
      *
      * @since 3.2
      */
     public static final String PT_PREFERENCES = "preferences"; //$NON-NLS-1$

     /**
      * An event object describing a change to a named property.
      * <p>
      * The preferences object reports property change events for internal state
      * changes that may be of interest to external parties. A special listener
      * interface (<code>Preferences.IPropertyChangeListener</code>) is
      * defined for this purpose. Listeners are registered via the
      * <code>Preferences.addPropertyChangeListener</code> method.
      * </p>
      * <p>
      * Clients cannot instantiate or subclass this class.
      * </p>
      *
      * @see Preferences#addPropertyChangeListener(Preferences.IPropertyChangeListener)
      * @see Preferences.IPropertyChangeListener
      */
     public static class PropertyChangeEvent extends EventObject {
         /**
          * All serializable objects should have a stable serialVersionUID
          */
         private static final long serialVersionUID = 1L;

         /**
          * The name of the changed property.
          */
         private String propertyName;

         /**
          * The old value of the changed property, or <code>null</code> if
          * not known or not relevant.
          */
         private Object oldValue;

         /**
          * The new value of the changed property, or <code>null</code> if
          * not known or not relevant.
          */
         private Object newValue;

         /**
          * Creates a new property change event.
          *
          * @param source the object whose property has changed
          * @param property the property that has changed (must not be
          * <code>null</code>)
          * @param oldValue the old value of the property, or
          * <code>null</code> if none
          * @param newValue the new value of the property, or
          * <code>null</code> if none
          */
         protected PropertyChangeEvent(Object source, String property, Object oldValue, Object newValue) {

             super(source);
             if (property == null) {
                 throw new IllegalArgumentException ();
             }
             this.propertyName = property;
             this.oldValue = oldValue;
             this.newValue = newValue;
         }

         /**
          * Returns the name of the property that changed.
          * <p>
          * Warning: there is no guarantee that the property name returned
          * is a constant string. Callers must compare property names using
          * <code>equals</code>, not ==.
          *</p>
          *
          * @return the name of the property that changed
          */
         public String getProperty() {
             return propertyName;
         }

         /**
          * Returns the new value of the property.
          *
          * @return the new value, or <code>null</code> if not known
          * or not relevant
          */
         public Object getNewValue() {
             return newValue;
         }

         /**
          * Returns the old value of the property.
          *
          * @return the old value, or <code>null</code> if not known
          * or not relevant
          */
         public Object getOldValue() {
             return oldValue;
         }
     }

     /**
      * Listener for property changes.
      * <p>
      * Usage:
      * <pre>
      * Preferences.IPropertyChangeListener listener =
      * new Preferences.IPropertyChangeListener() {
      * public void propertyChange(Preferences.PropertyChangeEvent event) {
      * ... // code to deal with occurrence of property change
      * }
      * };
      * emitter.addPropertyChangeListener(listener);
      * ...
      * emitter.removePropertyChangeListener(listener);
      * </pre>
      * </p>
      * <p>
      * <em>Note:</em> Depending on the means in which the property
      * values changed, the old and new values for the property can
      * be either typed, a string representation of the value, or <code>null</code>.
      * Clients who wish to behave properly in all cases should all
      * three cases in their implementation of the property change listener.
      * </p>
      */
     public interface IPropertyChangeListener extends EventListener {

         /**
          * Notification that a property has changed.
          * <p>
          * This method gets called when the observed object fires a property
          * change event.
          * </p>
          *
          * @param event the property change event object describing which
          * property changed and how
          */
         public void propertyChange(Preferences.PropertyChangeEvent event);
     }

     /**
      * List of registered listeners (element type:
      * <code>IPropertyChangeListener</code>).
      * These listeners are to be informed when the current value of a property
      * changes.
      */
     protected ListenerList listeners = new ListenerList();

     /**
      * The mapping from property name to
      * property value (represented as strings).
      */
     private Properties properties;

     /**
      * The mapping from property name to
      * default property value (represented as strings);
      * <code>null</code> if none.
      */
     private Properties defaultProperties;

     /**
      * Indicates whether a value has been changed by <code>setToDefault</code>
      * or <code>setValue</code>; initially <code>false</code>.
      */
     protected boolean dirty = false;

     /**
      * Exports all non-default-valued preferences for all installed plugins to the
      * provided file. If a file already exists at the given location, it will be deleted.
      * If there are no preferences to export, no file will be written.
      * <p>
      * The file that is written can be read later using the importPreferences method.
      * </p>
      * @param path The absolute file system path of the file to export preferences to.
      * @exception CoreException if this method fails. Reasons include:
      * <ul>
      * <li> The file could not be written.</li>
      * </ul>
      * @see #importPreferences(IPath)
      * @see #validatePreferenceVersions(IPath)
      */
     public static void exportPreferences(IPath path) throws CoreException {
         File file = path.toFile();
         if (file.exists())
             file.delete();
         file.getParentFile().mkdirs();
         IPreferencesService service = PreferencesService.getDefault();
         OutputStream output = null;
         FileOutputStream fos = null;
         try {
             fos = new FileOutputStream(file);
             output = new BufferedOutputStream(fos);
             IEclipsePreferences node = (IEclipsePreferences) service.getRootNode().node(InstanceScope.SCOPE);
             service.exportPreferences(node, output, (String []) null);
             output.flush();
             fos.getFD().sync();
         } catch (IOException e) {
             String message = NLS.bind(PrefsMessages.preferences_errorWriting, file, e.getMessage());
             IStatus status = new Status(IStatus.ERROR, PrefsMessages.OWNER_NAME, IStatus.ERROR, message, e);
             throw new CoreException(status);
         } finally {
             if (output != null)
                 try {
                     output.close();
                 } catch (IOException e) {
                     // ignore
 }
         }
     }

     /**
      * Loads the plugin preferences from the given file, and replaces all
      * non-default-valued preferences for all plugins with the values from this file.
      * <p>
      * If the file contains preferences for plug-ins that don't exist in the current
      * install, they are ignored. This method does not validate if the plug-in
      * versions in the preference file match the currently installed plug-ins.
      * Clients should first call validatePreferenceVersions on the file to ensure
      * that the versions are compatible.
      * </p>
      * <p>
      * The file must have been written by the exportPreferences method.
      * </p>
      * @param path The absolute file system path of the file to import preferences from.
      * @exception CoreException if this method fails. Reasons include:
      * <ul>
      * <li> The file does not exist.</li>
      * <li> The file could not be read.</li>
      * </ul>
      * @see #exportPreferences(IPath)
      * @see #validatePreferenceVersions(IPath)
      */
     public static void importPreferences(IPath path) throws CoreException {
         if (!path.toFile().exists()) {
             String msg = NLS.bind(PrefsMessages.preferences_fileNotFound, path.toOSString());
             throw new CoreException(new Status(IStatus.ERROR, PrefsMessages.OWNER_NAME, 1, msg, null));
         }
         IPreferencesService service = PreferencesService.getDefault();
         InputStream input = null;
         try {
             input = new BufferedInputStream(new FileInputStream(path.toFile()));
             service.importPreferences(input);
         } catch (FileNotFoundException e) {
             String msg = NLS.bind(PrefsMessages.preferences_fileNotFound, path.toOSString());
             throw new CoreException(new Status(IStatus.ERROR, PrefsMessages.OWNER_NAME, 1, msg, e));
         } finally {
             if (input != null)
                 try {
                     input.close();
                 } catch (IOException e) {
                     // ignore
 }
         }
     }

     /**
      * Validates that the preference versions in the given file match the versions
      * of the currently installed plugins. Returns an OK status if all preferences match
      * the currently installed plugins, otherwise a MultiStatus describing what
      * plugins have preferences that don't match.
      * <p>
      * If the returned status has a <code>IStatus.WARNING</code> severity,
      * it means that some preferences may not be applicable but for the most
      * part they will be compatible. If the returned status has a
      * <code>IStatus.ERROR</code> severity, it means that the preferences
      * will probably not be compatible.
      * <p>
      * If the file contains preferences for plug-ins that don't exist in the current
      * install, they are ignored.
      * </p>
      * <p>
      * The file must have been written by the exportPreferences method.
      * </p>
      * @param file The absolute file system path of the preference file to validate.
      * @see #exportPreferences(IPath)
      * @see #importPreferences(IPath)
      */
     public static IStatus validatePreferenceVersions(IPath file) {
         PreferencesService service = PreferencesService.getDefault();
         return service.validateVersions(file);
     }

     /**
      * Creates an empty preference table.
      * <p>
      * Use the methods <code>load(InputStream)</code> and
      * <code>store(InputStream)</code> to load and store these preferences.
      * </p>
      * @see #load(InputStream)
      * @see #store(OutputStream, String)
      */
     public Preferences() {
         defaultProperties = new Properties();
         properties = new Properties(defaultProperties);
     }

     /**
      * Adds a property change listener to this preference object.
      * Has no affect if the identical listener is already registered.
      * <p>
      * <em>Note:</em> Depending on the means in which the property
      * values changed, the old and new values for the property can
      * be either typed, a string representation of the value, or <code>null</code>.
      * Clients who wish to behave properly in all cases should all
      * three cases in their implementation of the property change listener.
      * </p>
      * @param listener a property change listener
      */
     public void addPropertyChangeListener(IPropertyChangeListener listener) {
         listeners.add(listener);
     }

     /**
      * Removes the given listener from this preference object.
      * Has no affect if the listener is not registered.
      *
      * @param listener a property change listener
      */
     public void removePropertyChangeListener(IPropertyChangeListener listener) {
         listeners.remove(listener);
     }

     /**
      * Returns whether the given property is known to this preference object,
      * either by having an explicit setting or by having a default
      * setting. Returns <code>false</code> if the given name is <code>null</code>.
      *
      * @param name the name of the property, or <code>null</code>
      * @return <code>true</code> if either a current value or a default
      * value is known for the named property, and <code>false</code>otherwise
      */
     public boolean contains(String name) {
         return (properties.containsKey(name) || defaultProperties.containsKey(name));
     }

     /**
      * Fires a property change event corresponding to a change to the
      * current value of the property with the given name.
      *
      * @param name the name of the property, to be used as the property
      * in the event object
      * @param oldValue the old value, or <code>null</code> if not known or not
      * relevant
      * @param newValue the new value, or <code>null</code> if not known or not
      * relevant
      */
     protected void firePropertyChangeEvent(String name, Object oldValue, Object newValue) {
         if (name == null)
             throw new IllegalArgumentException ();
         Object [] changeListeners = this.listeners.getListeners();
         // Do we even need to fire an event?
 if (changeListeners.length == 0)
             return;
         final PropertyChangeEvent pe = new PropertyChangeEvent(this, name, oldValue, newValue);
         for (int i = 0; i < changeListeners.length; ++i) {
             final IPropertyChangeListener l = (IPropertyChangeListener) changeListeners[i];
             ISafeRunnable job = new ISafeRunnable() {
                 public void handleException(Throwable exception) {
                     // already being logged in SafeRunner#run()
 }

                 public void run() throws Exception {
                     l.propertyChange(pe);
                 }
             };
             SafeRunner.run(job);
         }
     }

     /**
      * Returns the current value of the boolean-valued property with the
      * given name.
      * Returns the default-default value (<code>false</code>) if there
      * is no property with the given name, or if the current value
      * cannot be treated as a boolean.
      * The given name must not be <code>null</code>.
      *
      * @param name the name of the property
      * @return the boolean-valued property
      */
     public boolean getBoolean(String name) {
         String value = properties.getProperty(name);
         if (value == null) {
             return BOOLEAN_DEFAULT_DEFAULT;
         }
         return value.equals(Preferences.TRUE);
     }

     /**
      * Sets the current value of the boolean-valued property with the
      * given name. The given name must not be <code>null</code>.
      * <p>
      * A property change event is reported if the current value of the
      * property actually changes from its previous value. In the event
      * object, the property name is the name of the property, and the
      * old and new values are wrapped as objects.
      * </p>
      * <p>
      * If the given value is the same as the corresponding default value
      * for the given property, the explicit setting is deleted.
      * Note that the recommended way of re-initializing a property to its
      * default value is to call <code>setToDefault</code>.
      * </p>
      *
      * @param name the name of the property
      * @param value the new current value of the property
      */
     public void setValue(String name, boolean value) {
         boolean defaultValue = getDefaultBoolean(name);
         boolean oldValue = getBoolean(name);
         if (value == defaultValue) {
             Object removed = properties.remove(name);
             if (removed != null) {
                 // removed an explicit setting
 dirty = true;
             }
         } else {
             properties.put(name, value ? Preferences.TRUE : Preferences.FALSE);
         }
         if (oldValue != value) {
             // mark as dirty since value did really change
 dirty = true;
             // report property change if getValue now returns different value
 firePropertyChangeEvent(name, oldValue ? Boolean.TRUE : Boolean.FALSE, value ? Boolean.TRUE : Boolean.FALSE);
         }
     }

     /**
      * Returns the default value for the boolean-valued property
      * with the given name.
      * Returns the default-default value (<code>false</code>) if there
      * is no default property with the given name, or if the default
      * value cannot be treated as a boolean.
      * The given name must not be <code>null</code>.
      *
      * @param name the name of the property
      * @return the default value of the named property
      */
     public boolean getDefaultBoolean(String name) {
         String value = defaultProperties.getProperty(name);
         if (value == null) {
             return BOOLEAN_DEFAULT_DEFAULT;
         }
         return value.equals(Preferences.TRUE);
     }

     /**
      * Sets the default value for the boolean-valued property with the
      * given name. The given name must not be <code>null</code>.
      * <p>
      * Note that the current value of the property is affected if
      * the property's current value was its old default value, in which
      * case it changes to the new default value. If the property's current
      * is different from its old default value, its current value is
      * unaffected. No property change events are reported by changing default
      * values.
      * </p>
      *
      * @param name the name of the property
      * @param value the new default value for the property
      */
     public void setDefault(String name, boolean value) {
         defaultProperties.put(name, value ? Preferences.TRUE : Preferences.FALSE);
     }

     /**
      * Returns the current value of the double-valued property with the
      * given name.
      * Returns the default-default value (<code>0.0</code>) if there
      * is no property with the given name, or if the current value
      * cannot be treated as a double.
      * The given name must not be <code>null</code>.
      *
      * @param name the name of the property
      * @return the double-valued property
      */
     public double getDouble(String name) {
         return convertToDouble(properties.getProperty(name), DOUBLE_DEFAULT_DEFAULT);
     }

     /**
      * Sets the current value of the double-valued property with the
      * given name. The given name must not be <code>null</code>.
      * <p>
      * A property change event is reported if the current value of the
      * property actually changes from its previous value. In the event
      * object, the property name is the name of the property, and the
      * old and new values are wrapped as objects.
      * </p>
      * <p>
      * If the given value is the same as the corresponding default value
      * for the given property, the explicit setting is deleted.
      * Note that the recommended way of re-initializing a property to its
      * default value is to call <code>setToDefault</code>.
      * </p>
      *
      * @param name the name of the property
      * @param value the new current value of the property; must be
      * a number (not a NaN)
      */
     public void setValue(String name, double value) {
         if (Double.isNaN(value)) {
             throw new IllegalArgumentException ();
         }
         double defaultValue = getDefaultDouble(name);
         double oldValue = getDouble(name);
         if (value == defaultValue) {
             Object removed = properties.remove(name);
             if (removed != null) {
                 // removed an explicit setting
 dirty = true;
             }
         } else {
             properties.put(name, Double.toString(value));
         }
         if (oldValue != value) {
             // mark as dirty since value did really change
 dirty = true;
             // report property change if getValue now returns different value
 firePropertyChangeEvent(name, new Double (oldValue), new Double (value));
         }
     }

     /**
      * Returns the default value for the double-valued property
      * with the given name.
      * Returns the default-default value (<code>0.0</code>) if there
      * is no default property with the given name, or if the default
      * value cannot be treated as a double.
      * The given name must not be <code>null</code>.
      *
      * @param name the name of the property
      * @return the default value of the named property
      */
     public double getDefaultDouble(String name) {
         return convertToDouble(defaultProperties.getProperty(name), DOUBLE_DEFAULT_DEFAULT);
     }

     /**
      * Sets the default value for the double-valued property with the
      * given name. The given name must not be <code>null</code>.
      * <p>
      * Note that the current value of the property is affected if
      * the property's current value was its old default value, in which
      * case it changes to the new default value. If the property's current
      * is different from its old default value, its current value is
      * unaffected. No property change events are reported by changing default
      * values.
      * </p>
      *
      * @param name the name of the property
      * @param value the new default value for the property; must be
      * a number (not a NaN)
      */
     public void setDefault(String name, double value) {
         if (Double.isNaN(value)) {
             throw new IllegalArgumentException ();
         }
         defaultProperties.put(name, Double.toString(value));
     }

     /**
      * Converts the given raw property value string to a double.
      *
      * @param rawPropertyValue the raw property value, or <code>null</code>
      * if none
      * @param defaultValue the default value
      * @return the raw value converted to a double, or the given
      * <code>defaultValue</code> if the raw value is <code>null</code> or
      * cannot be parsed as a double
      */
     private double convertToDouble(String rawPropertyValue, double defaultValue) {
         double result = defaultValue;
         if (rawPropertyValue != null) {
             try {
                 result = Double.parseDouble(rawPropertyValue);
             } catch (NumberFormatException e) {
                 // raw value cannot be treated as one of these
 }
         }
         return result;
     }

     /**
      * Returns the current value of the float-valued property with the
      * given name.
      * Returns the default-default value (<code>0.0f</code>) if there
      * is no property with the given name, or if the current value
      * cannot be treated as a float.
      * The given name must not be <code>null</code>.
      *
      * @param name the name of the property
      * @return the float-valued property
      */
     public float getFloat(String name) {
         return convertToFloat(properties.getProperty(name), FLOAT_DEFAULT_DEFAULT);
     }

     /**
      * Sets the current value of the float-valued property with the
      * given name. The given name must not be <code>null</code>.
      * <p>
      * A property change event is reported if the current value of the
      * property actually changes from its previous value. In the event
      * object, the property name is the name of the property, and the
      * old and new values are wrapped as objects.
      * </p>
      * <p>
      * If the given value is the same as the corresponding default value
      * for the given property, the explicit setting is deleted.
      * Note that the recommended way of re-initializing a property to its
      * default value is to call <code>setToDefault</code>.
      * </p>
      *
      * @param name the name of the property
      * @param value the new current value of the property; must be
      * a number (not a NaN)
      */
     public void setValue(String name, float value) {
         if (Float.isNaN(value)) {
             throw new IllegalArgumentException ();
         }
         float defaultValue = getDefaultFloat(name);
         float oldValue = getFloat(name);
         if (value == defaultValue) {
             Object removed = properties.remove(name);
             if (removed != null) {
                 // removed an explicit setting
 dirty = true;
             }
         } else {
             properties.put(name, Float.toString(value));
         }
         if (oldValue != value) {
             // mark as dirty since value did really change
 dirty = true;
             // report property change if getValue now returns different value
 firePropertyChangeEvent(name, new Float (oldValue), new Float (value));
         }
     }

     /**
      * Returns the default value for the float-valued property
      * with the given name.
      * Returns the default-default value (<code>0.0f</code>) if there
      * is no default property with the given name, or if the default
      * value cannot be treated as a float.
      * The given name must not be <code>null</code>.
      *
      * @param name the name of the property
      * @return the default value of the named property
      */
     public float getDefaultFloat(String name) {
         return convertToFloat(defaultProperties.getProperty(name), FLOAT_DEFAULT_DEFAULT);
     }

     /**
      * Sets the default value for the float-valued property with the
      * given name. The given name must not be <code>null</code>.
      * <p>
      * Note that the current value of the property is affected if
      * the property's current value was its old default value, in which
      * case it changes to the new default value. If the property's current
      * is different from its old default value, its current value is
      * unaffected. No property change events are reported by changing default
      * values.
      * </p>
      *
      * @param name the name of the property
      * @param value the new default value for the property; must be
      * a number (not a NaN)
      */
     public void setDefault(String name, float value) {
         if (Float.isNaN(value)) {
             throw new IllegalArgumentException ();
         }
         defaultProperties.put(name, Float.toString(value));
     }

     /**
      * Converts the given raw property value string to a float.
      *
      * @param rawPropertyValue the raw property value, or <code>null</code>
      * if none
      * @param defaultValue the default value
      * @return the raw value converted to a float, or the given
      * <code>defaultValue</code> if the raw value is <code>null</code> or
      * cannot be parsed as a float
      */
     private float convertToFloat(String rawPropertyValue, float defaultValue) {
         float result = defaultValue;
         if (rawPropertyValue != null) {
             try {
                 result = Float.parseFloat(rawPropertyValue);
             } catch (NumberFormatException e) {
                 // raw value cannot be treated as one of these
 }
         }
         return result;
     }

     /**
      * Returns the current value of the integer-valued property with the
      * given name.
      * Returns the default-default value (<code>0</code>) if there
      * is no property with the given name, or if the current value
      * cannot be treated as an integer.
      * The given name must not be <code>null</code>.
      *
      * @param name the name of the property
      * @return the int-valued property
      */
     public int getInt(String name) {
         return convertToInt(properties.getProperty(name), INT_DEFAULT_DEFAULT);
     }

     /**
      * Sets the current value of the integer-valued property with the
      * given name. The given name must not be <code>null</code>.
      * <p>
      * A property change event is reported if the current value of the
      * property actually changes from its previous value. In the event
      * object, the property name is the name of the property, and the
      * old and new values are wrapped as objects.
      * </p>
      * <p>
      * If the given value is the same as the corresponding default value
      * for the given property, the explicit setting is deleted.
      * Note that the recommended way of re-initializing a property to its
      * default value is to call <code>setToDefault</code>.
      * </p>
      *
      * @param name the name of the property
      * @param value the new current value of the property
      */
     public void setValue(String name, int value) {
         int defaultValue = getDefaultInt(name);
         int oldValue = getInt(name);
         if (value == defaultValue) {
             Object removed = properties.remove(name);
             if (removed != null) {
                 // removed an explicit setting
 dirty = true;
             }
         } else {
             properties.put(name, Integer.toString(value));
         }
         if (oldValue != value) {
             // mark as dirty since value did really change
 dirty = true;
             // report property change if getValue now returns different value
 firePropertyChangeEvent(name, new Integer (oldValue), new Integer (value));
         }
     }

     /**
      * Returns the default value for the integer-valued property
      * with the given name.
      * Returns the default-default value (<code>0</code>) if there
      * is no default property with the given name, or if the default
      * value cannot be treated as an integer.
      * The given name must not be <code>null</code>.
      *
      * @param name the name of the property
      * @return the default value of the named property
      */
     public int getDefaultInt(String name) {
         return convertToInt(defaultProperties.getProperty(name), INT_DEFAULT_DEFAULT);
     }

     /**
      * Sets the default value for the integer-valued property with the
      * given name. The given name must not be <code>null</code>.
      * <p>
      * Note that the current value of the property is affected if
      * the property's current value was its old default value, in which
      * case it changes to the new default value. If the property's current
      * is different from its old default value, its current value is
      * unaffected. No property change events are reported by changing default
      * values.
      * </p>
      *
      * @param name the name of the property
      * @param value the new default value for the property
      */
     public void setDefault(String name, int value) {
         defaultProperties.put(name, Integer.toString(value));
     }

     /**
      * Converts the given raw property value string to an int.
      *
      * @param rawPropertyValue the raw property value, or <code>null</code>
      * if none
      * @param defaultValue the default value
      * @return the raw value converted to an int, or the given
      * <code>defaultValue</code> if the raw value is <code>null</code> or
      * cannot be parsed as an int
      */
     private int convertToInt(String rawPropertyValue, int defaultValue) {
         int result = defaultValue;
         if (rawPropertyValue != null) {
             try {
                 result = Integer.parseInt(rawPropertyValue);
             } catch (NumberFormatException e) {
                 // raw value cannot be treated as one of these
 }
         }
         return result;
     }

     /**
      * Returns the current value of the long-valued property with the
      * given name.
      * Returns the default-default value (<code>0L</code>) if there
      * is no property with the given name, or if the current value
      * cannot be treated as a long.
      * The given name must not be <code>null</code>.
      *
      * @param name the name of the property
      * @return the long-valued property
      */
     public long getLong(String name) {
         return convertToLong(properties.getProperty(name), LONG_DEFAULT_DEFAULT);
     }

     /**
      * Sets the current value of the long-valued property with the
      * given name. The given name must not be <code>null</code>.
      * <p>
      * A property change event is reported if the current value of the
      * property actually changes from its previous value. In the event
      * object, the property name is the name of the property, and the
      * old and new values are wrapped as objects.
      * </p>
      * <p>
      * If the given value is the same as the corresponding default value
      * for the given property, the explicit setting is deleted.
      * Note that the recommended way of re-initializing a property to its
      * default value is to call <code>setToDefault</code>.
      * </p>
      *
      * @param name the name of the property
      * @param value the new current value of the property
      */
     public void setValue(String name, long value) {
         long defaultValue = getDefaultLong(name);
         long oldValue = getLong(name);
         if (value == defaultValue) {
             Object removed = properties.remove(name);
             if (removed != null) {
                 // removed an explicit setting
 dirty = true;
             }
         } else {
             properties.put(name, Long.toString(value));
         }
        if (oldValue != value) {
            // mark as dirty since value did really change
 dirty = true;
            // report property change if getValue now returns different value
 firePropertyChangeEvent(name, new Long (oldValue), new Long (value));
        }
    }

    /**
     * Returns the default value for the long-valued property
     * with the given name.
     * Returns the default-default value (<code>0L</code>) if there
     * is no default property with the given name, or if the default
     * value cannot be treated as a long.
     * The given name must not be <code>null</code>.
     *
     * @param name the name of the property
     * @return the default value of the named property
     */
    public long getDefaultLong(String name) {
        return convertToLong(defaultProperties.getProperty(name), LONG_DEFAULT_DEFAULT);
    }

    /**
     * Sets the default value for the long-valued property with the
     * given name. The given name must not be <code>null</code>.
     * <p>
     * Note that the current value of the property is affected if
     * the property's current value was its old default value, in which
     * case it changes to the new default value. If the property's current
     * is different from its old default value, its current value is
     * unaffected. No property change events are reported by changing default
     * values.
     * </p>
     *
     * @param name the name of the property
     * @param value the new default value for the property
     */
    public void setDefault(String name, long value) {
        defaultProperties.put(name, Long.toString(value));
    }

    /**
     * Converts the given raw property value string to a long.
     *
     * @param rawPropertyValue the raw property value, or <code>null</code>
     * if none
     * @param defaultValue the default value
     * @return the raw value converted to a long, or the given
     * <code>defaultValue</code> if the raw value is <code>null</code> or
     * cannot be parsed as a long
     */
    private long convertToLong(String rawPropertyValue, long defaultValue) {
        long result = defaultValue;
        if (rawPropertyValue != null) {
            try {
                result = Long.parseLong(rawPropertyValue);
            } catch (NumberFormatException e) {
                // raw value cannot be treated as one of these
 }
        }
        return result;
    }

    /**
     * Returns the current value of the string-valued property with the
     * given name.
     * Returns the default-default value (the empty string <code>""</code>)
     * if there is no property with the given name.
     * The given name must not be <code>null</code>.
     *
     * @param name the name of the property
     * @return the string-valued property
     */
    public String getString(String name) {
        String value = properties.getProperty(name);
        return (value != null ? value : STRING_DEFAULT_DEFAULT);
    }

    /**
     * Sets the current value of the string-valued property with the
     * given name. The given name must not be <code>null</code>.
     * <p>
     * A property change event is reported if the current value of the
     * property actually changes from its previous value. In the event
     * object, the property name is the name of the property, and the
     * old and new values are wrapped as objects.
     * </p>
     * <p>
     * If the given value is the same as the corresponding default value
     * for the given property, the explicit setting is deleted.
     * Note that the recommended way of re-initializing a property to its
     * default value is to call <code>setToDefault</code>.
     * </p>
     *
     * @param name the name of the property
     * @param value the new current value of the property
     */
    public void setValue(String name, String value) {
        if (value == null) {
            throw new IllegalArgumentException ();
        }
        String defaultValue = getDefaultString(name);
        String oldValue = getString(name);
        if (value.equals(defaultValue)) {
            Object removed = properties.remove(name);
            if (removed != null) {
                // removed an explicit setting
 dirty = true;
            }
        } else {
            properties.put(name, value);
        }
        if (!oldValue.equals(value)) {
            // mark as dirty since value did really change
 dirty = true;
            // report property change if getValue now returns different value
 firePropertyChangeEvent(name, oldValue, value);
        }
    }

    /**
     * Returns the default value for the string-valued property
     * with the given name.
     * Returns the default-default value (the empty string <code>""</code>)
     * is no default property with the given name, or if the default
     * value cannot be treated as a string.
     * The given name must not be <code>null</code>.
     *
     * @param name the name of the property
     * @return the default value of the named property
     */
    public String getDefaultString(String name) {
        String value = defaultProperties.getProperty(name);
        return (value != null ? value : STRING_DEFAULT_DEFAULT);
    }

    /**
     * Sets the default value for the string-valued property with the
     * given name. The given name must not be <code>null</code>.
     * <p>
     * Note that the current value of the property is affected if
     * the property's current value was its old default value, in which
     * case it changes to the new default value. If the property's current
     * is different from its old default value, its current value is
     * unaffected. No property change events are reported by changing default
     * values.
     * </p>
     *
     * @param name the name of the property
     * @param value the new default value for the property
     */
    public void setDefault(String name, String value) {
        if (value == null) {
            throw new IllegalArgumentException ();
        }
        defaultProperties.put(name, value);
    }

    /**
     * Returns whether the property with the given name has the default value in
     * virtue of having no explicitly set value.
     * Returns <code>false</code> if the given name is <code>null</code>.
     *
     * @param name the name of the property, or <code>null</code>
     * @return <code>true</code> if the property has no explicitly set value,
     * and <code>false</code> otherwise (including the case where the property
     * is unknown to this object)
     */
    public boolean isDefault(String name) {
        return !properties.containsKey(name);
    }

    /**
     * Sets the current value of the property with the given name back
     * to its default value. Has no effect if the property does not have
     * its own current value. The given name must not be <code>null</code>.
     * <p>
     * Note that the recommended way of re-initializing a property to the
     * appropriate default value is to call <code>setToDefault</code>.
     * This is implemented by removing the named value from the object,
     * thereby exposing the default value.
     * </p>
     * <p>
     * A property change event is always reported. In the event
     * object, the property name is the name of the property, and the
     * old and new values are either strings, or <code>null</code>
     * indicating the default-default value.
     * </p>
     *
     * @param name the name of the property
     */
    public void setToDefault(String name) {
        Object oldPropertyValue = properties.remove(name);
        if (oldPropertyValue != null) {
            dirty = true;
        }
        String newValue = defaultProperties.getProperty(name, null);
        // n.b. newValue == null if there is no default value
 // can't determine correct default-default without knowing type
 firePropertyChangeEvent(name, oldPropertyValue, newValue);
    }

    /**
     * Returns a list of all properties known to this preference object which
     * have current values other than their default value.
     *
     * @return an array of property names
     */
    public String [] propertyNames() {
        return (String []) properties.keySet().toArray(EMPTY_STRING_ARRAY);
    }

    /**
     * Returns a list of all properties known to this preference object which
     * have an explicit default value set.
     *
     * @return an array of property names
     */
    public String [] defaultPropertyNames() {
        return (String []) defaultProperties.keySet().toArray(EMPTY_STRING_ARRAY);
    }

    /**
     * Returns whether the current values in this preference object
     * require saving.
     *
     * @return <code>true</code> if at least one of the properties
     * known to this preference object has a current value different from its
     * default value, and <code>false</code> otherwise
     */
    public boolean needsSaving() {
        return dirty;
    }

    /**
     * Saves the non-default-valued properties known to this preference object to
     * the given output stream using
     * <code>Properties.store(OutputStream,String)</code>.
     * <p>
     * Note that the output is unconditionally written, even when
     * <code>needsSaving</code> is <code>false</code>.
     * </p>
     *
     * @param out the output stream
     * @param header a comment to be included in the output, or
     * <code>null</code> if none
     * @exception IOException if there is a problem saving this preference object
     * @see Properties#store(OutputStream,String)
     */
    public void store(OutputStream out, String header) throws IOException {
        properties.store(out, header);
        dirty = false;
    }

    /**
     * Loads the non-default-valued properties for this preference object from the
     * given input stream using
     * <code>java.util.Properties.load(InputStream)</code>. Default property
     * values are not affected.
     *
     * @param in the input stream
     * @exception IOException if there is a problem loading this preference
     * object
     * @see java.util.Properties#load(InputStream)
     */
    public void load(InputStream in) throws IOException {
        properties.load(in);
        dirty = false;
    }
}

